/* 
 * File:   main.c
 * Author: ossi, joni
 *
 * Created on October 21, 2011, 6:55 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include "dynarray.h"
#include "hashtable.h"
#include "colours.h"
#include "person.h"
#include "date.h"
#include "familytree.h"
#include "jsonparser.h"
#include "fileio.h"
#include "main.h"

/*
 * Lukee näppäimistöltä merkin, ja pitää huolta,
 * että stdin bufferi on sen jälkeen tyhjä
 */
wchar_t get_char() { // Ossi
    wchar_t f, c;
    f = c = getwchar();
    while (c != L'\n' && c != EOF) {
        c = getwchar();
    }
    return f;
}

void prompt() { //Ossi
    putwchar(L'>');
    putwchar(L' ');
}

/*
 *	Poistaa whitespacen wstringin alusta ja lopusta
 */
wchar_t* trim(wchar_t* t) { //Ossi
    int len = wcslen(t);
    if (len == 0) return t;
    int i;

    for (i = 0; (i < len && isspace(t[i])); ++i); //lasketaan alussa olevan whitespacen pituus

    if (i == len - 1) return t; // koko merkkijono oli whitespacea

    for (int j = 0; (j < i) && (j + i < len); ++j) {
        t[j] = t[j + i]; //siirretään kirjaimia whitespacen pituuden verran vasemmalle 
        t[j+i] = L'\0';  //(kirjaimet alkavat kohdasta i eli whitespacen pituus
    }                   // eli whitespacen loppu
    for (int j = len - 1; isspace(t[j]); --j) { //Nollataan lopussa oleva whitespace
        t[j] = L'\0';
    }

    return t;
}

/*
 *	Pyytää käyttäjältä stringin
 */
wchar_t* get_string(wchar_t* prompt_text, int x, int y) { //Ossi
    wchar_t* tmp = malloc(sizeof (wchar_t)*100);
    if (tmp == NULL) {
        return NULL;
    }
    *tmp = L'\0';
    move_cursor(x, y);
    print_colour(prompt_text, FG_WHITE);
    move_cursor(x, y + 1);
    prompt();
    fgetws(tmp, 100, stdin);
    return tmp;
}

/*
 *	Pyytää käyttäjältä päivämäärän
 */
date get_date(wchar_t* prompt_text, int x, int y) { //Ossi
    move_cursor(x, y);
    print_colour(prompt_text, FG_WHITE);
    move_cursor(x, y + 1);
    wprintf(L"                    "); //tyhjennetään "tekstikenttä" jos edellistä tekstiä jäänyt paikalle
    // ei tyhjennä välttämättä kaikkea, mutta yksinkertaisin tapa
    move_cursor(x, y + 1);
    prompt();

    wchar_t in_str[15];
    fgetws(in_str, 14, stdin);
    date d = date_from_string(in_str);
    return d;
}

/*
 *	Pyytää käyttäjältä merkin
 */
char get_char_prompt(wchar_t* prompt_text, int x, int y) { //Ossi
    move_cursor(x, y);
    print_colour(prompt_text, FG_WHITE);
    move_cursor(x, y + 1);
    wprintf(L"                      ");
    move_cursor(x, y + 1);
    prompt();
    return get_char();
}

/*
 *	Pyytää käyttäjältä komennon
 */
wchar_t get_command() { //Ossi
    prompt();
    return get_char();
}

/**
 * Näyttää sivutetun listan parametrina annetuista henkilöistä.
 * @param title Valinnassa näytettävä kehote
 * @param da person* -osoittimia sisältävä dynarray
 * @param choice kertoo, onko lista interaktiivinen
 * @return valitun henkilön indeksi, tai -1 jos lista on tyhjä
 */
int listpersons(wchar_t* title, dynarray* da, bool choice) { //Ossi
    unsigned int page = 0;
    unsigned int ppp = 10; // persons per page
    while (true) {
        clear_screen();

        move_cursor(2, 2);
        print_colour(title, FG_WHITE);

        if (da->top != 0) {
            unsigned int lastperson = ((page + 1) * ppp < da->top) ? (page + 1) * ppp : da->top;

            for (unsigned int i = page * ppp; i < lastperson; ++i) {
                move_cursor(2, 4 + (i % 10));

                const char* fg = (i % 2) ? FG_WHITE : FG_BLACK;
                const char* bg = (i % 2) ? BG_BLACK : BG_GRAY;
                set_colour_bg(fg, bg);

                person* tmp = (person*) da_get(da, i);

                wprintf(L"%d. %Ls", (i % 10), tmp->name);
                reset_colour();
            }

            move_cursor(2, 16);
            set_colour(FG_WHITE);
            wprintf(L"Sivu %d/%d", page + 1, da->top / ppp + 1);
            reset_colour();

            move_cursor(2, 18);
            if (choice) {
                print_colour(L"Numero valitsee henkilön, '<' ja '>' selaavat sivuja. q = tyhjä valinta/peruuta", FG_WHITE);
            } else {
                print_colour(L"'<' ja '>' selaavat sivuja. q = poistu", FG_WHITE);
            }
            move_cursor(2, 19);
            wchar_t command = get_command();

            if (choice && isdigit(command)) {
                unsigned int index = (command - L'0') + page*ppp;
                if (index >= da->top) continue;
                return index; // palautetaan strings[page*ppp+command]
            }

            switch (command) {
                case L'<':
                    if (page > 0) --page;
                    break;
                case L'>':
                    if (page < (da->top / ppp)) ++page;
                    break;
                case L'q':
                    return -1;
            }

        } else {
            move_cursor(2, 4);
            wprintf(L"Ei henkilöitä");
            move_cursor(2, 18);
            print_colour(L"'q' palaa valikkoon", FG_WHITE);
            move_cursor(2, 19);
            wchar_t command = get_command();
            switch (command) {
                case L'q':
                    return -1;
            }
        }

    }
}

/*
 *	Näyttää henkilön jälkeläiset
 */
void showoffspring(person* person) { //Joni
    dynarray* da = per_get_offspring(person, NULL);
    listpersons(L"Jälkeläiset:", da, false);
    da_free(da);
}

/*
 *	Näyttää henkilön esivanhemmat
 */
void showancestor(person* person) { //Joni
    dynarray* da = per_get_ancestor(person, NULL);
    listpersons(L"Esivanhemmat:", da, false);
    da_free(da);
}

/*
 *	Näyttää henkilön puolisot
 */
void showpartner(person* person) { //Ossi
    dynarray* da = per_get_partner(person);
    listpersons(L"Puolisot:", da, false);
    da_free(da);
}

/*
 *	Näyttää henkilön Muut suhteet (avoliitot)
 */
void showothers(person* person) { //Ossi
    dynarray* da = per_get_other_rel(person);
    listpersons(L"Avoliitot:", da, false);
    da_free(da);
}

/*
 *	Lisää suhteen (avo- tai avioliitto) kahden aiemmin määritellyn henkilön välille
 */
void addrelation(person* person) { //Joni
    clear_screen();
    move_cursor(2, 2);
    wprintf(L"Suhteen lisäys henkilölle %ls", person->name);

    wchar_t type;
    do {
        type = get_char_prompt(L"Suhteen tyyppi('1' avioliitto, '2' avoliitto):", 2, 4);
    } while (type != L'1' && type != L'2');

    int partner_index;
    do {
        partner_index = listpersons(L"Valitse kumppani", FT, true);
    } while ((partner_index != -1) && per_compare(da_get(FT, partner_index), person) == 0);
    if (partner_index == -1) {
        return;
    }
    per_add_relation(person, da_get(FT, partner_index), int_to_relt((int) (type - L'0')));
}

/*
 *	Pyytää käyttäjältä uuden syntymäpäivän ja muuttaa sen personin syntymäpäiväksi
 */
void setbirthdate(person* p) { //Joni
    clear_screen();
    move_cursor(2, 2);
    wprintf(L"Muuta henkilön %ls syntymäpäivää:", p->name);
    date bd = NULL_DATE;
    while (date_compare(&bd, &NULL_DATE) == 0) {
        bd = get_date(L"Syntymäpäivä (dd-mm-yyyy)", 4, 4);
        if (date_compare(&bd, &NULL_DATE) == 0) {
            move_cursor(4, 6);
            print_colour(L"Virheellinen päivä, muoto dd-mm-yyyy", FG_RED);
        } else {
            move_cursor(4, 6);
            print_colour(L"                                    ", FG_BLACK);
        }
    }
    per_set_birthday(p, bd);
    viewperson(p);
}

/*
 *	Pyytää käyttäjältä kuolinäivän ja muuttaa sen personin kuolinpäiväksi
 */
void setdeathdate(person* p) { //Joni
    clear_screen();
    move_cursor(2, 2);
    wprintf(L"Muuta henkilön %ls kuolinpäivää:", p->name);
    date dd = NULL_DATE;
    while (date_compare(&dd, &NULL_DATE) == 0) {
        dd = get_date(L"Kuolinpäivä (dd-mm-yyyy)", 4, 4);
        if (date_compare(&dd, &NULL_DATE) == 0) {
            move_cursor(4, 6);
            print_colour(L"Virheellinen päivä, muoto dd-mm-yyyy", FG_RED);
        } else {
            move_cursor(4, 6);
            print_colour(L"                                    ", FG_BLACK);
        }
    }
    per_set_deathday(p, dd);
    viewperson(p);
}

/*
 *		Lisää uuden henkilön, joko itsenään tai annetun personin lapsena
 */
void addperson(person* parent) { //Ossi
    clear_screen();
    move_cursor(20, 2);
    print_colour(L"Lisää henkilö", FG_WHITE);
    wchar_t* name;
    person existing;
    bool askagain;
    do {
        askagain = false;
        name = get_string(L"Nimi", 16, 6);
        existing.name = trim(name);

        if (ft_person_exists(&existing)) {
            move_cursor(16, 8);
            print_colour(L"Henkilö on olemassa", FG_RED);
            move_cursor(16, 7);
            print_colour(L"                    ", FG_BLACK);
            askagain = true;
            free(name);
        } else if (wcscmp(existing.name,L"") == 0) { 
            move_cursor(16, 8);
            print_colour(L"Lisää henkilön nimi", FG_RED);
            move_cursor(16, 7);
            print_colour(L"                    ", FG_BLACK);
            askagain = true;
            free(name);
		} else {
            move_cursor(16, 8);
            print_colour(L"                            ", FG_BLACK);
        }
    } while (askagain);


    wchar_t gender = get_char_prompt(L"Sukupuoli (m/n)", 16, 9);

    date bd = NULL_DATE;
    while (date_compare(&bd, &NULL_DATE) == 0) {
        bd = get_date(L"Syntymäpäivä (dd-mm-yyyy)", 16, 12);
        if (date_compare(&bd, &NULL_DATE) == 0) {
            move_cursor(16, 14);
            print_colour(L"Virheellinen päivä, muoto dd-mm-yyyy", FG_RED);
        } else {
            move_cursor(16, 14);
            print_colour(L"                                    ", FG_BLACK);
        }
    }
    date dd = NULL_DATE;

    dd = get_date(L"Kuolinpäivä (muu kuin pvm = ei päivää)", 16, 15);

    int new_person_index = ft_add_person(name, bd, dd, gender);

    if (parent != NULL) {
        int parent2_index;
        do {
            parent2_index = listpersons(L"Valitse toinen vanhempi('q' tekee yksinhuoltajan)", FT, true);
        } while ((parent2_index != -1) && ((per_compare(da_get(FT, parent2_index), da_get(FT, new_person_index)) == 0) || (per_compare(da_get(FT, parent2_index), parent) == 0)));
        if (parent2_index == -1) {
            per_add_child_singleparent(parent, da_get(FT, new_person_index));
        } else {
            per_add_child(parent, da_get(FT, parent2_index), da_get(FT, new_person_index));
        }
    }
}

/*
 *	Muuntaa wchar_t* char* muuttaen wide merkit 'x'-merkeiksi
 */
char* wchars_tochars(wchar_t* string) { //Joni
    char* return_string = malloc(sizeof (char) *20);
    if (return_string != NULL) {
        int i = 0;
        while (string[i] != L'\0' && string[i] != L'\n' && i < 19) {
            if ((int) string[i] < 256) return_string[i] = (char) string[i];
            else return_string[i] = 'x';
            ++i;
        }
        return_string[19] = '\0';
    }
    return return_string;
}

/*
 *	Pyytää käyttäjältä tiedostonimen ja tallentaa nykyisen FamilyTree:n tiedostoon
 */
void export() { //Joni	
    clear_screen();

    move_cursor(2, 2);
    print_colour(L"Tallenna tiedostoon ('q' palaa valikkoon)", FG_WHITE);
    wchar_t* wfilename = get_string(L"Tiedostonimi (tyhjä: FamilyTree.json):", 2, 3);
    char* filename;
    if (wfilename[0] == L'q' && (wfilename[1] == EOF || wfilename[1] == L'\n')) {
        free(wfilename);
        return;
    } else {
        if (wfilename[0] == EOF || wfilename[0] == L'\n' || wfilename[0] == L'\0') {
            wcscpy(wfilename, L"FamilyTree.json\0");
        }

        filename = wchars_tochars(wfilename);

        save_familytree(filename);
        free(wfilename);
        free(filename);
    }

}

/*
 *	Poistaa käyttäjän valitseman henkilön
 */
void removeperson() { //Ossi
    move_cursor(2, 6);
    int index = listpersons(L"Valitse poistettava henkilö", FT, true);
    if (index == -1) {
        return;
    }
    ft_remove_person(da_get(FT, (unsigned int) index));
}

/*
 *	Näyttää tarkemmat tiedot käyttäjän valitsemasta henkilöstä
 */
void viewperson(person* p) { //Ossi
    if (p == NULL) {
        int pers = listpersons(L"Valitse henkilö", FT, true);
        if (pers == -1) return;
        p = (person*) da_get(FT, pers);
    }
    while (true) {
        clear_screen();
        move_cursor(4, 2);
        print_colour_bg(p->name, FG_WHITE, BG_GREEN);

        set_colour(FG_WHITE);
        move_cursor(4, 4);
        wprintf(L"Sukupuoli: %c", p->gender);
        move_cursor(4, 5);
        wprintf(L"Syntymäpäivä: %d-%d-%d", p->birthdate.day, p->birthdate.month, p->birthdate.year);

        if (date_compare(&p->deathdate, &NULL_DATE)) {
            move_cursor(4, 6);
            wprintf(L"Kuolinpäivä: %d-%d-%d", p->deathdate.day, p->deathdate.month, p->deathdate.year);
        }
        reset_colour();

        move_cursor(4, 8);
        print_colour(L"a. Muuta syntymäpäivä", FG_WHITE);
        move_cursor(4, 9);
        print_colour(L"b. Muuta kuolinpäivä", FG_WHITE);
        move_cursor(4, 10);
        print_colour(L"c. Lisää lapsi", FG_WHITE);
        move_cursor(4, 11);
        print_colour(L"d. Lisää suhde", FG_WHITE);
        move_cursor(4, 12);
        print_colour(L"e. Näytä jälkeläiset", FG_WHITE);
        move_cursor(4, 13);
        print_colour(L"f. Näytä esivanhemmat", FG_WHITE);
        move_cursor(4, 14);
        print_colour(L"g. Näytä puoliso(t)", FG_WHITE);
        move_cursor(4, 15);
        print_colour(L"h. Näytä avoliitot", FG_WHITE);
        move_cursor(4, 17);
        print_colour(L"q. Päävalikko", FG_WHITE);



        move_cursor(4, 18);


        wchar_t command = get_command();
        switch (command) {
            case L'a':
                setbirthdate(p);
                break;
            case L'b':
                setdeathdate(p);
                break;
            case L'c':
                addperson(p);
                break;
            case L'd':
                addrelation(p);
                break;
            case L'e':
                showoffspring(p);
                break;
            case L'f':
                showancestor(p);
                break;
            case L'g':
                showpartner(p);
                break;
            case L'h':
                showothers(p);
                break;
            case L'q':
                return;
        }
    }
}

/*
 *	Tulostaa menun
 */
void print_menu() { //Ossi
    while (true) {
        clear_screen();
        move_cursor(20, 2);
        print_colour(L"Sukupuu -ohjelma", FG_WHITE);
        move_cursor(16, 6);
        print_colour(L"a. Lisää Henkilö", FG_WHITE);
        move_cursor(16, 7);
        print_colour(L"b. Poista Henkilö", FG_WHITE);
        move_cursor(16, 8);
        print_colour(L"c. Listaa Henkilöt", FG_WHITE);
        move_cursor(16, 9);
        print_colour(L"d. Tallenna tiedostoon", FG_WHITE);
        move_cursor(16, 11);
        print_colour(L"q. Lopeta", FG_WHITE);
        move_cursor(12, 13);

        wchar_t command = get_command();
        switch (command) {
            case L'a':
                addperson(NULL);
                break;
            case L'b':
                removeperson();
                break;
            case L'c':
                viewperson(NULL);
                break;
            case L'd':
                export();
                break;
            case L'q':
                return;
        }
    }
}

void showhelp() { //Ossi
    wprintf(L"Sukupuu -ohjelma v .1\nKäyttö: jvov-sukupuu [options]\n\n Optiot:\t -h näyttää tämän viestin\n\t -f <tiedostonimi> lataa sukupuun tiedot annetusta tiedostosta");
}

/*
 * Tulostaa tiedostoa ladattaessa tapahtuneet virheet
 */
void print_load_errors(int flags, bool fnf) { //Ossi
    bool inv = ((flags & STATE_INVALID_DATA) != 0);	
    bool part = ((flags & STATE_PARTIAL) != 0);
    bool unkwn = ((flags & STATE_UNKNOWN) != 0);
    if (fnf || part || inv || unkwn) {
        clear_screen();
        move_cursor(2, 2);
        wprintf(L"Tiedoston latauksessa esiintyi ongelmia:");
        int line = 4;

        if (fnf) {
            move_cursor(2, line);
            print_colour(L"Tiedostoa ei löydy", FG_RED);
            ++line;
        }

        if (part) {
            move_cursor(2, line);
            print_colour(L"Puuttuvaa dataa", FG_YELLOW);
            ++line;
        }

        if (inv) {
            move_cursor(2, line);
            print_colour(L"Väärä tiedostomuoto", FG_RED);
            ++line;
        }

        if (unkwn) {
            move_cursor(2, line);
            print_colour(L"Tuntematon virhe (Väärä tiedostomuoto/tiedostoa ei voitu avata)", FG_RED);
            ++line;
        }

    }


}

/*
 * Tarkistaa tiedoston avattavuuen
 */
bool file_exists(char* filename) { //Ossi
    FILE* f;
    if ((f = fopen(filename, "r"))) {
        fclose(f);
        return true;
    }
    return false;
}

int main(int argc, char **argv) { //Ossi
    setlocale(LC_ALL, "en_GB.utf8");
    char* filename = NULL;
    int res = 0;
    ft_init();

    for (char** max = argv + argc; argv < max; ++argv) {
        if (strcmp(*argv, "-h") == 0) {
            showhelp();
            ft_free();
            return 0;
        }
        if (strcmp(*argv, "-f") == 0) {
            ++argv;
            if (argv < max) {
                bool fnf = false;
                filename = *argv;
                if (file_exists(filename)) {
                    res = load_familytree(filename);
                } else {
                    fnf = true;
                }
                print_load_errors(res, fnf);

            } else {
                ft_free();
                showhelp();
                return 1;
            }
        }

    }

    clear_screen();
    print_menu();

    ft_free();
    reset_console();
    return 0;
}


